Release 10.1A: OpenEdge Development:
Progress Dynamics Advanced Development


Putting the value check into the window code

Now that the example is complete, you can look at an alternative way to do the same thing. Consider the procedures that you have just created. Most of the work is done in a custom super procedure attached to the read-only Customer viewer on Page 1 of this window. It is important to keep in mind that you can define a custom super procedure for an object only at the master level, for the object itself, and not for a single instance of the object in a particular window. In this case, it might not be appropriate to have the dataAvailable code associated with that viewer, because the special behavior it defines is really specific to this one window. In fact, the code would generate numerous errors if the viewer were in a different window, because the code as it stands assumes various things about what objects are in the window and what links there are between them. This in itself is worth correcting by improving the code to deal gracefully with its context if the window design is changed in some way.

If you do not want to associate the behavior specifically with the custcommentsv viewer, you can instead tie it to the window by putting the code into the oemaintwinsuper.p procedure. Let’s look at that alternative and see what changes you have to make to the code in its new location.

First, look at the initializeObject procedure in oemaintwinsuper.p. The reason it seemed more natural to add the new behavior to the Customer viewer is that it required localizing the dataAvailable event, which the viewer subscribes to in the SDO. The container window does not subscribe to this event, and therefore does not normally receive it. This is the first thing you must change in order to move the code to the window.

You can intercept this event in the window simply by adding a statement to subscribe to it. So this new version of the initializeObject code has a few additional lines in it. The first line gets the handle of the Customer SDO by retrieving the Navigation-Target of the toolbar. The NavigationTarget property is of data type CHARACTER, so you must retrieve it into a CHARACTER variable and then convert that to a handle.

Note: The DYNAMIC-FUNCTION operation that takes place inside the {get} is forgiving enough that it actually casts the CHARACTER output from the getNavigationTarget function directly into the HANDLE variable hSDO. You would be well advised not to take advantage of the run-time engine’s generosity in this regard. In particular, if the {get} is turned into a direct lookup in the properties temp-table instead of a function call, you will get an error from your attempt to combine the two steps.

Keep in mind that the reason the NavigationTarget property is of type CHARACTER is that there could be more than one navigation target for the toolbar, in which case the multiple targets would be represented as a list. In this case there is only one, because at the time initializeObject gets run, only Page 0 and Page 1 of the folder have been created, so there is only one SDO in the window. Try not to make this kind of assumption in your own finished application code, so as to avoid errors if the window you are working with changes later on.

The code you must add in this initializeObject procedure is highlighted in bold, as shown:

Procedure initializeObject: 
/*------------------------------------------------------------------------- 
  Purpose:     Override of initializeObject to reset the TableIOType  
               property value to 'update' so that viewers are initially 
               disabled. 
  Parameters:  <none> 
-------------------------------------------------------------------------*/ 
DEFINE VARIABLE hToolbar AS HANDLE     NO-UNDO. 
DEFINE VARIABLE cSDO     AS CHARACTER  NO-UNDO. 
DEFINE VARIABLE hSDO     AS HANDLE     NO-UNDO. 
  {get ToolbarSource hToolbar}. 
  {set TableIoType 'update' hToolbar}. 
  {get NavigationTarget cSDO hToolbar}. 
  hSDO = WIDGET-HANDLE(cSDO). 
  SUBSCRIBE PROCEDURE TARGET-PROCEDURE TO 'dataAvailable' IN hSDO. 
  RUN SUPER. 
END PROCEDURE.  

Once you have the handle of the SDO you can subscribe to dataAvailable. Again, remember to subscribe the TARGET-PROCEDURE, not the super procedure itself. Now the window will get dataAvailable events just as the viewer does.

The next step is to move the dataAvailable code itself from the viewer’s super procedure to the window’s. You can then delete the viewer’s super procedure and remove its association from the viewer in the Repository Maintenance tool.

Because the dataAvailable code is now executed from the window’s perspective and not from the viewer’s, there are a few changes you must make. Let’s take a look at those.

Identifying an object among all the contained objects

First, just as with the code in initializeObject, this version of dataAvailable must locate the Customer SDO so that it can get the value of the Country field. And in this case, the warning about allowing for multiple Navigation-Targets becomes a reality. By the time this is executed, the other pages in the folder have likely been enabled, and the toolbar now has multiple Navigation-Targets. So you have no choice but to retrieve that value as a list and search through it for the Customer SDO.

Note: If you neglect to do this, you will get the error message, “Field Value too large for Integer,” because the run-time engine tries to convert some string such as 12345,24356,45675 into a handle, which is treated internally as a kind of integer.

You could do this by checking the LogicalObjectName property and comparing that to custcommentsv. This seems a little too restrictive, though, since it means that you must edit the code if you ever change the name of the viewer on Page 1. Since the code is checking the value of a field in the Customer table, it seems safer to check for the presence of the Customer table in the SDO, which is in its Tables property.

The code allows for the possibility that the Customer table might be joined to some other table in the SDO, in which case the Tables property would be a list, as shown:

ASSIGN hToolbar = DYNAMIC-FUNCTION ('getContainerToolbarSource' 
       IN TARGET-PROCEDURE) 
       cTargets = DYNAMIC-FUNCTION ('getNavigationTarget' IN hToolbar). 
  DO iTarget = 1 TO NUM-ENTRIES(cTargets): 
     hTarget = WIDGET-HANDLE(ENTRY(iTarget, cTargets)). 
     IF LOOKUP('Customer',  
                DYNAMIC-FUNCTION('getTables' IN hTarget)) NE 0 THEN      
     DO: 
         hDataSource = hTarget. 
         LEAVE. 
     END. 
 END. 

Scoping variables in super procedures

Here it is worth making another digression to discuss an important point. You might have realized that once initializeObject has determined the SDO handle, it could just stash it in a variable defined in the Definitions section of the super procedure, and then this code would not have to locate it again. This is true, but it is also very dangerous. In the context of the example, this would work fine, because the super procedure is serving only the oemaintwin window, so the value of the SDO handle would be valid for the life of the window. But what if there were some circumstance under which the user could run two copies of the window at the same time? This might easily be allowed in the application, and they would both share a single running instance of the super procedure. The value of the SDO handle saved away by one running instance of the window would not be valid for the other running instance. The kinds of errors that this results in can be very insidious and difficult to track down. Or look at another possibility. What if you realize later that another window requires similar support for disabling actions based on a value check? If you are doing your job right, you will not create a new procedure to handle that and just copy code from the first one. You will start with the existing procedure, take the elements that are subject to change (such as the name of the field and its value) and turn them into parameters or properties of the object, and let the single procedure work for all windows requiring this type of support.

Now it is very likely indeed that two different windows of this kind might be running at the same time, in which case they would be sharing a single instance of the super procedure, since much of the purpose of super procedures is to reduce memory use as well as r-code. The moral is that it is very bad form to hold any values across calls to different methods in a super procedure unless you are positive that a single instance of the super procedure will never be used to support two different objects at the same time.

Redirecting the PUBLISH statement from the window

The next step remains the same as before: Get the Country value and reset the cTargets variable to hold a list of the TableIO-Targets, as shown:

ASSIGN cCountry    = DYNAMIC-FUNCTION ('columnValue' IN hDataSource,'Country') 
       cTargets    = DYNAMIC-FUNCTION ('getTableIOTarget' IN hToolbar). 

Look at the next statement:

PUBLISH ‘resetTableIO’ FROM TARGET-PROCEDURE. 

When this code was in the viewer’s super procedure, the TARGET-PROCEDURE was the viewer itself. But now the code is executing from the window. Rather than going to the trouble of tracking down the handle of the custcommentsv viewer, it is simpler just to RUN resetTableIO directly in the toolbar, whose handle you do have, since this has exactly the same effect. Remember that a PUBLISH statement is basically the same as a RUN statement except that the procedure handle the event gets run in is not specified in the PUBLISH statement. So you should change the statement to this:

 RUN resetTableIO IN hToolbar. 

Reasons not to RUN SUPER from the window

The final change is also important to think about for a moment. Ordinarily, you must remember to include the RUN SUPER statement in your local procedure. Here you must remember to leave it out! The reason is that the window is not a native subscriber to the dataAvailable event at all. It just needs to intercept it to take some action on behalf of other objects. For this reason, no super procedure of the window implements dataAvailable, and if you try to invoke it with a RUN SUPER statement, you will get an error at run time, much as if you had put in a statement that said RUN dataAvailable IN <non-existent-handle>.

So, you must remove the RUN SUPER statement as you convert the code to work in the window’s super procedure, as shown:

/* RUN SUPER (pcRelative). */ 
END PROCEDURE. 

In some cases, especially where code is designed to work in a variety of situations, you might be faced with a case where there might or might not be a super procedure with the event in it. In this case it is perfectly OK to write RUN SUPER NO-ERROR.

Reasons not to use enableActions and disableActions

You might notice that there are two functions defined for toolbars in the Panel class called enableActions and disableActions. You might be tempted to run those functions directly rather than setting the DisabledActions property the example in this section described. This is generally not a good idea, as these functions are intended for internal use only.

In particular, they are very short-term in their effect. That is, if you use disableActions to disable, say, the Delete action, it will have an immediate effect, but as soon as any operation occurs that resets the toolbar, such as a page change, the action will be enabled again. This is probably not what you want, and this is why it is better to use the DisabledActions property to change the settings until you need to change them again.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095